// imports necessary for openGL
import processing.opengl.*;
import javax.media.opengl.*;

// import necessary for GLGraphics lib
import codeanticode.glgraphics.*;

class Scale
{
  private class GlowContribution
  {
    Scale s;
    float amount;
    
    GlowContribution(Scale _s, float _amount) { s = _s; amount = _amount; }
  }
  
  private class GlowConnection
  {
    Scale root;
    Vector parents;
    float targetGlow;
    float currentGlow;
    boolean receivedGlow;
    
    GlowConnection(Scale _root, float _targetGlow)
    {
      root = _root; targetGlow = _targetGlow; currentGlow = 0.0; receivedGlow = true;
      parents = new Vector();
    }
  }
  
  final int stateIdle = 0, stateGlowOn = 1, stateGlowOff = 2, stateGlowing = 3;
  final float glowSpeedOnTouch = 0.005, glowSpeedOffTouch = 0.0005;
  final float propogateThreshold = 0.5;
  
  private float x, y, rotation, scaleX, scaleY;
  private Scale [] parents;
  private Scale [] children;
  private Scale [] neighbours;
  private GLTexture tex;
  private int state;
  private float glowAmount;
  private float maxGlowAmount;
  private float glowDelta;
  private int glowState;
  private Scale glowParent;
  private Vector glowParents;
  private Scale  glowRoot;
  private Vector glowCxns;
  private boolean receivedGlow;
  private int numContributors;
  private int numTouches;
  private boolean isTouched;
  private ArrayList contributions;

  Scale(float _x, float _y, float sizeX, float sizeY, GLTexture _tex)
  {
    x = _x;
    y = _y;
    scaleX        = sizeX;
    scaleY        = sizeY;
    tex           = _tex;
    state         = stateIdle;
    glowAmount    = colorAmtScaleIdle;
    glowDelta     = 0.0;
    glowState = stateGlowOff;
    maxGlowAmount = 1.0;
    glowParent    = null;
    glowParents   = new Vector();
    glowRoot      = null;
    glowCxns      = new Vector();
    receivedGlow  = false;
    numContributors    = 0;
    numTouches    = 0;
    isTouched     = false;
    contributions = new ArrayList();
    
    parents       = new Scale[2];
    parents[0]    = null;
    parents[1]    = null;
    
    neighbours    = new Scale[2];
    neighbours[0] = null;
    neighbours[1] = null;
    
    children      = new Scale[2];
    children[0]   = null;
    children[1]   = null;
    
    rotation = random(-scaleRotationAmount,scaleRotationAmount);
  }
  
  void setNeighbours(Scale _parentL, Scale _parentR, Scale _neighbourL, Scale _neighbourR, Scale _childL, Scale _childR)
  {
    parents[0]    = _parentL;
    parents[1]    = _parentR;
    
    neighbours[0] = _neighbourL;
    neighbours[1] = _neighbourR;
    
    children[0]   = _childL;
    children[1]   = _childR;
  }
/*  
  static void initialize()
  {
     scaleTex  = new GLTexture(this, "scale.png");
  }
*/  
  void render(GLGraphicsOffScreen _gl)
  {
    float totalGlow = 0.0;
     
    for(int i = 0; i < glowCxns.size(); i++)
    {
      GlowConnection c = (GlowConnection)glowCxns.elementAt(i);
      totalGlow += c.currentGlow;
    }
    
    beginTransform();
     
    //int iGlowAmount  = ceil(min(maxGlowAmount,glowAmount) * 255.0);
    //int iGlowAmount = ceil(min(1.0,totalGlow) * 255.0);
    float colorRange = colorAmtScaleGlow - colorAmtScaleIdle;
    float colorAmount = colorAmtScaleIdle + min(1.0,totalGlow) * colorRange;
    int iColorAmount  = ceil(colorAmount * 255.0);
     
    _gl.tint(iColorAmount,iColorAmount,iColorAmount,255);
    _gl.image(tex, x, y, scaleX, scaleY);
     
     
    // render halo on top
    if(useHaloEffect == true)
    {
      int iHaloAmount = ceil((min(glowAmount,haloAmtScaleGlow) - colorAmtScaleIdle) / (colorAmtScaleGlow - colorAmtScaleIdle) * 255.0);
     
      _gl.tint(255,180,140,iHaloAmount);
      _gl.image(scaleGlowTex, x, y, scaleX, scaleY);
    }
     
    endTransform();
  }
  
  void spawnGlow(Scale root, float targetGlow)
  {
    GlowConnection cxn  = null;
    
    for(int i = 0; i < glowCxns.size(); i++)
    {
      GlowConnection c = (GlowConnection)glowCxns.elementAt(i);
      if(c.root == root)
        cxn  = c;
    }
    
    if(cxn == null)
    {
      
      cxn = new GlowConnection(root,targetGlow);
      glowCxns.add(cxn);
      receiveGlow(root,0.1);
    }
    else
    {
      //cxn.targetGlow = targetGlow;
    }
  }
  
  void receiveGlow(Scale root, float amount)
  {
    GlowConnection cxn  = null;
    
    for(int i = 0; i < glowCxns.size(); i++)
    {
      GlowConnection c = (GlowConnection)glowCxns.elementAt(i);
      if(c.root == root)
        cxn  = c;
    }
    
    if(cxn != null)
    {
      cxn.receivedGlow = true;
      
      float oldGlow = cxn.currentGlow;
      cxn.currentGlow += amount * cxn.targetGlow;
      if(cxn.currentGlow > cxn.targetGlow)
        cxn.currentGlow = cxn.targetGlow;
        
      if(cxn.parents.size() == 0 && oldGlow < cxn.targetGlow * 0.75 && cxn.currentGlow >= cxn.targetGlow * 0.75)
      {
        spreadToParents(cxn,cxn.targetGlow);
      }
      
      for(int i = 0; i < cxn.parents.size(); i++)
      {
        Scale s = (Scale)cxn.parents.elementAt(i);
        
        s.receiveGlow(root,amount);
      }
    }
    else
    {
      // no connection exists for this root, test for spawning new connection
      //cxn = new GlowConnection(root,targetGlow);
      //cxn.currentAmount
    }
    
  }

  void update(float milliseconds)
  {
    GlowConnection cxn  = null;
    
    for(int i = 0; i < glowCxns.size(); i++)
    {
      GlowConnection c = (GlowConnection)glowCxns.elementAt(i);

      if(c.receivedGlow == false)
      {
        // didn't receive glow this update, reduce current glow
        c.currentGlow -= milliseconds * glowSpeedOffTouch;
        
        if(c.currentGlow <= 0.0)
        {
          glowCxns.remove(c);
          i--;
        }
      }
      else
      {
        // reset 'receivedGlow' flag
        c.receivedGlow = false;
      }
    }
  }
  
  void touch(float milliseconds)
  {
    // increase this scale's glow amount
    
    //glowState = stateGlowOn;
    GlowConnection rootCxn = null;
    for(int i = 0; i < glowCxns.size(); i++)
    {
      GlowConnection c = (GlowConnection)glowCxns.elementAt(i);
      if(c.root == this)
      {
        rootCxn = c;
      }
    }
    
    if(rootCxn != null)
    {
      rootCxn.receivedGlow = true;
      float oldGlow = rootCxn.currentGlow;
      
      rootCxn.currentGlow += milliseconds * glowSpeedOnTouch;
      if(rootCxn.currentGlow > rootCxn.targetGlow)
        rootCxn.currentGlow = rootCxn.targetGlow;
        
      if(rootCxn.parents.size() == 0 && oldGlow < rootCxn.targetGlow && rootCxn.currentGlow >= rootCxn.targetGlow)
      {
        spreadToParents(rootCxn,rootCxn.targetGlow);
      }
      
      
      for(int i = 0; i < rootCxn.parents.size(); i++)
      {
        Scale p = (Scale)rootCxn.parents.elementAt(i);
        p.receiveGlow(this,milliseconds * glowSpeedOnTouch);
      }
    }
    else
    {
      // first time being touched, create glow connection
      rootCxn = new GlowConnection(this,1.0);
      rootCxn.currentGlow += milliseconds * glowSpeedOnTouch;
      glowCxns.add(rootCxn);
      
    }
  }
  
  void spreadToParents(GlowConnection cxn, float targetGlow)
  {
    // time to spread: pick parents to spread to
    int numParents = 0;
    if(parents[0] != null)
      numParents++;
    if(parents[1] != null)
      numParents++;
    
    if(numParents == 1)
    {
      Scale p = parents[0];
      if(parents[0] == null)
        p = parents[1];
      
      cxn.parents.add(p);
      p.spawnGlow(cxn.root,random(minGlowSpreadTarget * targetGlow,targetGlow));
    }
    else if(numParents == 2)
    {
      Scale p0 = parents[0];
      Scale p1 = parents[1];
      if(random(1.0) > 0.5)
      {
        p0 = parents[1];
        p1 = parents[0];
      }
        
      cxn.parents.add(p0);
      p0.spawnGlow(cxn.root,random(minGlowSpreadTarget * targetGlow,targetGlow));
      
      if(random(1.0) < glowBranchChance)
      {
        // spawn glow on other parent
        cxn.parents.add(p1);
        p1.spawnGlow(cxn.root,random(minGlowSpreadTarget * targetGlow,targetGlow));
      }
    }
  }
  
  boolean isTouched() { return glowState == stateGlowOn;  }
  
  boolean collides(float _x, float _y)
  {
    return _x >= x + scaleOverlapPercentX * scaleX / 2.0
        && _x <= x+scaleX - scaleOverlapPercentX * scaleX / 2.0
        && _y >= y + scaleY * 0.4
        && _y <= y+scaleY - scaleOverlapPercentY * scaleY * 0.15;
  }

  private void beginTransform()
  {
    //pushMatrix();
      //scale(scaleX,scaleY);
      //scale(1,-1);
      //translate(x,y);
      
      //rotate(rotation);
  }
  
  private void endTransform()
  {
   // popMatrix(); 
  }
}
